﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using iStudio.Structure.Control.Audio.Editor;
using iStudio.Structure.Wave;

namespace iStudio.Editor.SelectTool
{
    [Export (typeof(EditorTool))]
    public class EditorSelectTool : EditorTool
    {
        //
        // Repeat region selection elements
        //

        private readonly Rectangle _repeatRegion = new Rectangle();
        private readonly Polygon _repeatDragLeft = new Polygon();
        private readonly Polygon _repeatDragRight = new Polygon();

        //
        // Repeat region elements
        //

        private Point _mouseDownPoint;

        private double _endLoopRegion = -1;

        private bool _isMouseDown;
        private bool _isMouseSelectingRepeat;

        private double _startLoopRegion = -1;

        private Polygon _selectedDrag;

        //
        // Constants
        //

        private const int MouseMoveTolerance = 3;
        private const int IndicatorTriangleWidth = 4;

        //
        // Tool context menu
        //

        private ContextMenu _context;

        /// <summary>
        /// Handles sound player property changes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void SoundPlayerPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "SelectionBegin":
                    _startLoopRegion = SoundPlayer.SelectionBegin.TotalSeconds;
                    UpdateRepeatRegion ();
                    break;
                case "SelectionEnd":
                    _endLoopRegion = SoundPlayer.SelectionEnd.TotalSeconds;
                    UpdateRepeatRegion ();
                    break;
            }
        }

        /// <summary>
        /// Determines if point is in repeat region
        /// </summary>
        /// <param name="point">Point to check</param>
        /// <returns>True on success</returns>
        private bool PointInRepeatRegion(Point point)
        {
            return ( (point.X >= _repeatRegion.Margin.Left) && (point.X < (_repeatRegion.Margin.Left + _repeatRegion.Width)) && (_repeatRegion.Visibility == Visibility.Visible) );
        }

        private void UpdateRepeatRegion()
        {
            if ( (SoundPlayer == null) || (EditorCore.RepeatCanvas == null) )
            {
                return;
            }

            //
            // Add elements on canvas, if canvas was not yet done
            //

            if (EditorCore.RepeatCanvas.Children.Count == 0)
            {
                _repeatRegion.Height = EditorCore.RepeatCanvas.RenderSize.Height;
                _repeatRegion.Fill = EditorCore.RepeatRegionBrush;

                //
                // Draggers
                //
                _repeatDragLeft.Fill = new SolidColorBrush(Colors.LawnGreen);
                _repeatDragLeft.Points.Add(new Point (0.0, EditorCore.TimelineCanvas.RenderSize.Height));
                _repeatDragLeft.Points.Add(new Point (0.0 - IndicatorTriangleWidth, 10));
                _repeatDragLeft.Points.Add(new Point (0.0 + IndicatorTriangleWidth, 10));
                _repeatDragLeft.Points.Add(new Point (0.0, EditorCore.TimelineCanvas.RenderSize.Height));

                _repeatDragRight.Fill = new SolidColorBrush(Colors.LawnGreen);
                _repeatDragRight.Points.Add(new Point (0.0, EditorCore.TimelineCanvas.RenderSize.Height));
                _repeatDragRight.Points.Add(new Point (0.0 - IndicatorTriangleWidth, 10));
                _repeatDragRight.Points.Add(new Point (0.0 + IndicatorTriangleWidth, 10));
                _repeatDragRight.Points.Add(new Point (0.0, EditorCore.TimelineCanvas.RenderSize.Height));

                EditorCore.TimelineCanvas.Children.Add (_repeatDragLeft);
                EditorCore.TimelineCanvas.Children.Add (_repeatDragRight);
                EditorCore.RepeatCanvas.Children.Add (_repeatRegion);
            }
            
            //
            // Hide repeat region if project length is 0
            //

            // Calculate margins from repeat region
            double startLocation = _startLoopRegion * SoundPlayer.Project.Format.SampleRate / EditorCore.RenderFrameSize * EditorCore.PointThickness;
            double endLocation = _endLoopRegion * SoundPlayer.Project.Format.SampleRate / EditorCore.RenderFrameSize * EditorCore.PointThickness;

            if ( (Math.Abs(SoundPlayer.Project.Length) < Double.Epsilon) || (Math.Abs(endLocation - startLocation) < Double.Epsilon) )
            {
                _repeatDragLeft.Visibility = Visibility.Hidden;
                _repeatDragRight.Visibility = Visibility.Hidden;
                _repeatRegion.Visibility = Visibility.Hidden;

                return;
            }
            
            // Margin is start position offset
            _repeatRegion.Margin = new Thickness(startLocation, 0, 0, 0);

            // Width is calculated from start and end location
            _repeatRegion.Width = Math.Abs(endLocation - startLocation);

            _repeatDragLeft.Margin = new Thickness(startLocation, 0, 0, 0);
            _repeatDragRight.Margin = new Thickness(endLocation, 0, 0, 0);

            if (_repeatDragLeft.Visibility == Visibility.Hidden)
            {
                _repeatDragLeft.Visibility = Visibility.Visible;
                _repeatDragRight.Visibility = Visibility.Visible;
                _repeatRegion.Visibility = Visibility.Visible;
            }
        }

        /// <summary>
        /// Invoked when an unhandled MouseLeftButtonDown routed event is raised on this element. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="sender"> </param>
        /// <param name="mouseButtonEventArgs"> </param>
        protected void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            //
            // Check if SoundPlayer actually has a track on it
            //

            if (!IsEditorCoreInitialized() || !HasTracks())
            {
                return;
            }

            //
            // Remember down position
            //

            _isMouseDown = true;

            _mouseDownPoint = mouseButtonEventArgs.GetPosition(EditorCore.TimelineCanvas);

            //
            // Check if we are selecting repeat section -> click in TimelineCanvas
            //

            if ((_mouseDownPoint.X <= EditorCore.TimelineCanvas.ActualWidth) && (_mouseDownPoint.Y <= EditorCore.TimelineCanvas.RenderSize.Height))
            {
                EditorCore.CaptureMouse();

                _isMouseSelectingRepeat = true;
            }
        }

        /// <summary>
        /// Invoked when an unhandled MouseLeftButtonUp routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="sender"> </param>
        /// <param name="mouseButtonEventArgs"> </param>
        protected void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            //
            // Repeat region render
            //

            if (!_isMouseDown)
            {
                return;
            }

            _isMouseDown = false;
            _selectedDrag = null;

            Select(mouseButtonEventArgs);

            MoveChannelPosition(mouseButtonEventArgs);
        }

        protected void MoveChannelPosition (MouseButtonEventArgs mouseButtonEventArgs)
        {
            // If we are selecting repeat
            if (_isMouseSelectingRepeat)
            {
                _isMouseSelectingRepeat = false;

                bool updateRepeatRegion = false;

                EditorCore.ReleaseMouseCapture();

                //
                // Check for MouseMoveTolerance
                //

                if (Math.Abs(mouseButtonEventArgs.GetPosition(EditorCore.TimelineCanvas).X - _mouseDownPoint.X) < MouseMoveTolerance)
                {
                    //
                    // If the cursor was moved into position inside the point region, we only set the channel position
                    //

                    if (PointInRepeatRegion(_mouseDownPoint))
                    {
                        SoundPlayer.ChannelPosition = Math.Min(SoundPlayer.Project.Length, EditorCore.GetPositionInSeconds(_mouseDownPoint.X));
                    }

                    //
                    // Otherwise we delete repeat region
                    //

                    else
                    {
                        SoundPlayer.ChannelPosition = Math.Min(SoundPlayer.Project.Length, EditorCore.GetPositionInSeconds(mouseButtonEventArgs.GetPosition(EditorCore.WaveformCanvas).X));

                        SoundPlayer.SelectionBegin = TimeSpan.Zero;
                        SoundPlayer.SelectionEnd = TimeSpan.Zero;

                        _startLoopRegion = -1;
                        _endLoopRegion = -1;

                        updateRepeatRegion = true;
                    }
                }
                else
                {
                    SoundPlayer.SelectionBegin = TimeSpan.FromSeconds(_startLoopRegion);
                    SoundPlayer.SelectionEnd = TimeSpan.FromSeconds(_endLoopRegion);
                    SoundPlayer.ChannelPosition = Math.Min(SoundPlayer.Project.Length, _startLoopRegion);

                    updateRepeatRegion = true;
                }

                if (updateRepeatRegion)
                {
                    UpdateRepeatRegion();
                }
            }
        }

        /// <summary>
        /// Function selects tracks in track editor based on user click
        /// </summary>
        /// <param name="mouseButtonEventArgs"></param>
        protected void Select (MouseButtonEventArgs mouseButtonEventArgs)
        {
            if (_isMouseSelectingRepeat)
            {
                return;
            }

            //
            // Track selection in Waveform
            //

            Point mouseWaveform = mouseButtonEventArgs.GetPosition(EditorCore.WaveformCanvas);
            Point mouseTrack = mouseButtonEventArgs.GetPosition(EditorCore.TrackCanvas);

            int topMargin = (int)Math.Floor(mouseWaveform.Y / 40);

            //
            // Verify that waveformCanvas was clicked
            //

            if ((mouseWaveform.Y >= 0) && (mouseWaveform.X >= 0) && (mouseWaveform.X <= EditorCore.WaveformCanvas.RenderSize.Width) && (mouseWaveform.Y <= EditorCore.WaveformCanvas.RenderSize.Height))
            {
                //
                // Calculate which chunk was clicked
                //

                int leftMargin = (int)Math.Floor(mouseWaveform.X / EditorCore.PointThickness) * EditorCore.RenderFrameSize;

                int counter = 0;

                bool selected = false;

                foreach (WaveTrack track in SoundPlayer.Project.TrackList)
                {
                    // Found the track
                    if ((topMargin < (counter + track.WaveFormat.Channels)) && (topMargin >= counter))
                    {
                        //
                        // Find correct chunk
                        //

                        // Get channel
                        int channel = topMargin - counter;

                        // Find chunk with correct X position

                        foreach (WaveChunk chunk in track.TrackChunks)
                        {
                            if ((chunk.Channel == channel) && (leftMargin >= chunk.Offset) && (leftMargin <= (chunk.Offset + chunk.Length)))
                            {
                                chunk.IsSelected = !chunk.IsSelected;

                                selected = true;

                                break;
                            }
                        }

                        // End first foreach loop
                        break;
                    }

                    counter += track.WaveFormat.Channels;
                }

                //
                // Deselect if it was clicked random
                //

                if (!selected)
                {
                    foreach (WaveTrack track in SoundPlayer.Project.TrackList)
                    {
                        track.DeselectAllChunks();
                    }
                }
            }

            //
            // Track Canvas was clicked
            //

            else if ((mouseTrack.Y >= 0) && (mouseTrack.X >= 0) && (mouseTrack.X <= EditorCore.TrackCanvas.RenderSize.Width) && (mouseTrack.Y <= EditorCore.TrackCanvas.RenderSize.Height))
            {
                int counter = 0;

                bool selected = false;

                foreach (WaveTrack track in SoundPlayer.Project.TrackList)
                {
                    // Found the track
                    if ((topMargin < (counter + track.WaveFormat.Channels)) && (topMargin >= counter))
                    {
                        if (track.IsSelected)
                        {
                            track.DeselectAllChunks();
                        }
                        else
                        {
                            track.SelectAllChunks();
                        }

                        selected = true;

                        break;
                    }

                    counter += track.WaveFormat.Channels;
                }

                //
                // Deselect if it was clicked random
                //

                if (!selected)
                {
                    foreach (WaveTrack track in SoundPlayer.Project.TrackList)
                    {
                        track.DeselectAllChunks();
                    }
                }
            }
        }
        
        /// <summary>
        /// Invoked when an unhandled Mouse.MouseRightButtonUp attached event reaches an element in its route that is derived from this class.
        /// </summary>
        /// <param name="e">The MouseEventArgs that contains the event data.</param>
        /*protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseRightButtonUp(e);

            if (_context == null)
            {
                _context = new ContextMenu();

                MenuInitializer.InitializeMenu(_context, Manager.Manager.Instance.PluginManager.Modules, ModuleCommandType.FromCommandId("TrackEditorSelect"));

                _waveformCanvas.ContextMenu = _context;
            }

            Point mouse = e.GetPosition(_waveformCanvas);

            if ((mouse.X >= 0) && (mouse.X <= _waveformCanvas.RenderSize.Width) && (mouse.Y >= 0) && (mouse.Y <= _waveformCanvas.RenderSize.Height))
            {
                _context.IsOpen = true;
            }
        }*/

        /// <summary>
        /// Invoked when an unhandled Mouse.MouseMove attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">The MouseEventArgs that contains the event data.</param>
        protected void OnMouseMove(object o, MouseEventArgs mouseEventArgs)
        {
            //
            // Check if SoundPlayer actually has a track on it
            //

            if (!IsEditorCoreInitialized() || !HasTracks())
            {
                return;
            }

            //
            // Mouse is moved check if we are selecting repeat and if user mouse is pushed
            //

            if (_isMouseDown && _isMouseSelectingRepeat)
            {
                EditorCore.Cursor = Cursors.SizeWE;

                // Waveform canvas or timeline canvas can be used
                Point currentPoint = mouseEventArgs.GetPosition(EditorCore.WaveformCanvas);

                //
                // If user dragged the region position, we change the position
                //

                if (_selectedDrag == null)
                {
                    if (Math.Abs(currentPoint.X - _mouseDownPoint.X) > MouseMoveTolerance)
                    {
                        //
                        // Depends on which way the mouse is moved, that start and end selection are set
                        //

                        if (_mouseDownPoint.X < currentPoint.X)
                        {
                            _startLoopRegion = EditorCore.GetPositionInSeconds(_mouseDownPoint.X);
                            _endLoopRegion = EditorCore.GetPositionInSeconds(currentPoint.X);
                        }
                        else
                        {
                            _startLoopRegion = EditorCore.GetPositionInSeconds(currentPoint.X);
                            _endLoopRegion = EditorCore.GetPositionInSeconds(_mouseDownPoint.X);
                        }
                    }
                    else
                    {
                        _startLoopRegion = -1;
                        _endLoopRegion = -1;
                    }
                }
                else
                {
                    if (_selectedDrag == _repeatDragLeft)
                    {
                        _startLoopRegion = EditorCore.GetPositionInSeconds(currentPoint.X);
                    }
                    else if (_selectedDrag == _repeatDragRight)
                    {
                        _endLoopRegion = EditorCore.GetPositionInSeconds(currentPoint.X);
                    }
                    else
                    {
                        _startLoopRegion = -1;
                        _endLoopRegion = -1;
                    }
                }

                UpdateRepeatRegion();
            }

            //
            // Handling cursor when mouse is down
            //

            else if (!_isMouseDown)
            {
                Point currentPoint = mouseEventArgs.GetPosition(EditorCore.TimelineCanvas);

                //
                // Timeline canvas had hand for cursor
                //

                if ((currentPoint.X <= EditorCore.TimelineCanvas.ActualWidth) && (currentPoint.Y <= EditorCore.TimelineCanvas.RenderSize.Height))
                {
                    _selectedDrag = null;
                    VisualTreeHelper.HitTest(EditorCore.TimelineCanvas, FilterCallback, ResultCallback, new PointHitTestParameters(Mouse.GetPosition(EditorCore.TimelineCanvas)));
                    
                    if (_selectedDrag != null)
                    {
                        EditorCore.Cursor = Cursors.SizeWE;
                    }
                    else
                    {
                        EditorCore.Cursor = Cursors.Hand;
                        _selectedDrag = null;
                    }
                }
                //
                // Other parts have normal arrow
                //
                else
                {
                    EditorCore.Cursor = Cursors.Arrow;
                }
            }
        }

        /// <summary>
        /// HitTest Filter behaviour for draggers
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        private HitTestFilterBehavior FilterCallback(DependencyObject target)
        {
            if (target.GetType().IsAssignableFrom(typeof(Polygon)))
            {
                _selectedDrag = target as Polygon;
                return HitTestFilterBehavior.Stop;
            }
            
            return HitTestFilterBehavior.Continue;
        }

        private HitTestResultBehavior ResultCallback(HitTestResult result)
        {
            return HitTestResultBehavior.Stop;
        }

        /// <summary>
        /// Render size is changed of the control, certain objects might need to be changed
        /// </summary>
        /// <param name="sizeInfo"></param>
        public override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            //
            // Fix repeat canvas height
            //

            if (EditorCore.RepeatCanvas.Children.Count > 0)
            {
                _repeatRegion.Height = sizeInfo.NewSize.Height;
            }
        }

        protected override void RegisterTrackEditorEvents ()
        {
            EditorCore.MouseLeftButtonUp += OnMouseLeftButtonUp;
            EditorCore.MouseLeftButtonDown += OnMouseLeftButtonDown;
            EditorCore.MouseMove += OnMouseMove;
        }

        protected override void RegisterAudioEngineEvents ()
        {

        }
    }
}
